"""
Stat model
"""
import re

from google.appengine.ext import ndb

VALID_CONTEXT = re.compile(r'[a-zA-Z0-9_-]{1,100}')
VALID_STAT_NAME = re.compile(r'[a-zA-Z0-9_]{0,100}') # follows BigQuery column naming rules

class Stat(ndb.Model):
    """
    General purpose stats model.
    """
    _use_cache    = False
    _use_memcache = False

    context   = ndb.ComputedProperty(lambda self: self.extract_context(self.key))
    stat_name = ndb.ComputedProperty(lambda self: self.extract_stat_name(self.key))
    row_num   = ndb.ComputedProperty(lambda self: self.extract_row_num(self.key))
    value     = ndb.StringProperty(indexed=False) # may be NULL from bigquery
    dimension = ndb.StringProperty(repeated=True, name='d')

    def get_dimension_value(self, dimension_name):
        """
        Returns the dimesion value for the name.
        """
        for d in self.dimension:
            name, value = self.decode_dimension(d)
            if name == dimension_name:
                return value
        return None

    @classmethod
    def build_key(cls, context, stat_name, row_num):
        """
        Build a key
        """
        if not context:
            raise ValueError('context is required.')
        if not stat_name:
            raise ValueError('stat_name is required.')
        if not isinstance(row_num, int):
            raise ValueError('row_num is required and must be an int.')
        if not VALID_CONTEXT.match(context):
            raise ValueError('context must match %s' % VALID_CONTEXT)
        if not VALID_STAT_NAME.match(stat_name):
            raise ValueError('stat_name must match %s' % VALID_STAT_NAME)
        return ndb.Key(cls, 'S:%s:%s:%s' % (context, stat_name, row_num))

    @classmethod
    def extract_context(cls, key):
        """
        Pulls the context out from the key.
        """
        if not isinstance(key, ndb.Key):
            raise ValueError('key is required and must be an ndb.Key.')
        parts = key.id().split(':')
        return parts[1]

    @classmethod
    def extract_stat_name(cls, key):
        """
        Pulls the stat_name out from the key.
        """
        if not isinstance(key, ndb.Key):
            raise ValueError('key is required and must be an ndb.Key.')
        parts = key.id().split(':')
        return parts[2]

    @classmethod
    def extract_row_num(cls, key):
        """
        Pulls the row_num out from the key.
        """
        if not isinstance(key, ndb.Key):
            raise ValueError('key is required and must be an ndb.Key.')
        parts = key.id().split(':')
        return int(parts[3])

    @classmethod
    def create(cls, context, stat_name, row_num, value, dimensions=None, put=True):
        """
        Creates, and optionally saves, a stat entity.
        """
        if dimensions is not None and not isinstance(dimensions, list):
            dimensions = [dimensions]
        key = cls.build_key(context, stat_name, row_num)
        entity = cls(key=key, value=value, dimension=dimensions)
        if put:
            entity.put()
        return entity

    @classmethod
    def encode_dimension(cls, dimension_name, dimension_value):
        """
        Builds an encoded dimension string.
        """
        return '%s:%s' % (dimension_name, dimension_value)

    @classmethod
    def decode_dimension(cls, encoded_dimension):
        """
        Returns the original name/value as a tuple (name, value).
        """
        dimension_name, _, dimension_value = encoded_dimension.partition(':')
        return dimension_name, dimension_value
